////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011-2012 www.iflytek.com. All Rights Reserved.
//  This software for customer relationship management system, developed by Iflytek-ZY-GCZX team.
//  Software code and design for the team, copy rights reserved.
////////////////////////////////////////////////////////////////////////////////

package com.iflytek.ci.qams.service.impl;

import java.io.IOException;
import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.iflytek.ci.qams.dao.IssueDao;
import com.iflytek.ci.qams.domain.Issue;
import com.iflytek.ci.qams.service.FullTextService;
import com.iflytek.framework.lucene.Indexs;
import com.iflytek.framework.lucene.Searchs;
import com.iflytek.framework.pagination.PaginationCriteria;
import com.iflytek.framework.pagination.PaginationResult;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.iflytek.ci.qams.dto.IssueFulltext.DESCRIPTION;
import static com.iflytek.ci.qams.dto.IssueFulltext.PAGE_ONE;
import static com.iflytek.ci.qams.dto.IssueFulltext.PKEY;
import static com.iflytek.ci.qams.dto.IssueFulltext.PREFIXHTML;
import static com.iflytek.ci.qams.dto.IssueFulltext.SUFFIXHTML;
import static com.iflytek.ci.qams.dto.IssueFulltext.SUMMARY;
import static com.iflytek.ci.qams.dto.IssueFulltext.ZERO;

/**
 * <p>
 * 全文检索服务实现.
 * </p>
 * <p/>
 * 对于标志符（主键、文件名）、问题号码以及用于排序的文本域，使用 {@link org.apache.lucene.document.StringField}、{@link org.apache.lucene.document.LongField}等字段，也就是不需要分词、需要存储等选项
 * 对于问题标题、摘要文本，使用{@link org.apache.lucene.document.TextField}这种需要进行分词，且进行存储的Lucene Field。
 * 对于文档描述或者正文等文本，使用{@link org.apache.lucene.document.TextField}
 * 对于文档类型或者问题类型以及数据库主键，如果没有用于搜索的话，则值进行存储，不进行搜索的选项
 *
 * @author poplar.yfyang
 * @version 1.0 2012-11-24 3:30 PM
 * @since JDK 1.5
 */
@Service("fulltextService")
public class FullTextServiceImpl implements FullTextService {
	/** 日志处理 */
	private static final Logger LOGGER = LoggerFactory.getLogger(FullTextService.class);
	/** 文本高亮设置,模板 */
	private final SimpleHTMLFormatter _simple_html_formatter = new SimpleHTMLFormatter(PREFIXHTML, SUFFIXHTML);
	/** 文本高亮字数，默认100个 */
	private final SimpleFragmenter _simpleFragmenter = new SimpleFragmenter();
	/** 索引服务 */
	@Autowired
	private Indexs indexs;
	/** 检索服务 */
	@Autowired
	private Searchs searchs;
	/** 分词器 */
	@Autowired
	private Analyzer analyzer;
	/** 问题DAO */
	@Autowired
	private IssueDao issueDao;

	/**
	 * 获取表示日期的毫秒数，如果日期为空，返回0
	 *
	 * @param date 日期
	 * @return 毫秒数
	 */
	private static long dateNullLong(Date date) {
		return date == null ? PAGE_ONE : date.getTime();
	}

	/**
	 * 如果给定的字符串为null，返回空字符串，否则返回本身。
	 *
	 * @param str 字符串
	 * @return 如果为null，返回空字符串
	 */
	private static String stringNullDefault(String str) {
		return str == null ? "" : str;
	}

	/**
	 * 对字段进行高亮设置
	 *
	 * @param field       高亮字段名称
	 * @param document    文档名称
	 * @param highlighter 高亮器
	 * @return 高亮后的文本
	 * @throws IOException
	 * @throws InvalidTokenOffsetsException
	 */
	private static String highlight(String field, Document document, Highlighter highlighter)
			throws IOException, InvalidTokenOffsetsException {
		String text = document.getField(field).stringValue();
		TokenStream tokenStream = new SimpleAnalyzer(Version.LUCENE_40).tokenStream(PKEY, new StringReader(text));
		String highlighter_str = highlighter.getBestFragment(tokenStream, text);
		return Strings.isNullOrEmpty(highlighter_str) ? text : highlighter_str;
	}

	@Override
	public void create_index(final Issue issue) {
		//todo 多线程处理,由于lucene的索引目录问题，导致目前无法处理多线程的情况下处理索引目录，需要等待lucene的扩展framework来支持
		final Document document = new Document();
		document.add(new StringField(PKEY, issue.getPkey(), Field.Store.YES));
		document.add(new IntField("priopity", issue.getPriopity(), Field.Store.YES));
		document.add(new IntField("issuetype", issue.getIssuetype(), Field.Store.YES));
		document.add(new StringField("pname", stringNullDefault(issue.getPname()), Field.Store.YES));
		document.add(new TextField(SUMMARY, issue.getSummary(), Field.Store.YES));
		document.add(new TextField(DESCRIPTION, issue.getDescription(), Field.Store.NO));
		document.add(new TextField("environment", stringNullDefault(issue.getEnvironment()), Field.Store.NO));
		//排序需要存储
		document.add(new LongField("created", dateNullLong(issue.getCreated()), Field.Store.YES));
		document.add(new LongField("updated", dateNullLong(issue.getUpdated()), Field.Store.YES));
		document.add(new StringField("reporter", stringNullDefault(issue.getReporter()), Field.Store.YES));
		document.add(new StringField("reporterMail", stringNullDefault(issue.getReporterMail()), Field.Store.NO));
		document.add(new StringField("assignee", stringNullDefault(issue.getAssignee()), Field.Store.NO));
		document.add(new StringField("assigneeName", stringNullDefault(issue.getAssigneeName()), Field.Store.YES));
		//删除索引的字段标记
		final Term key_term = new Term(PKEY, issue.getPkey());
		indexs.create_index(document, key_term);

	}

	@Override
	public void remove_index(Issue issue) {
		final Term term = new Term(PKEY, issue.getPkey());
		indexs.remove_index(term);
	}

	@Override
	public PaginationResult<Issue> search(PaginationCriteria criteria, String keyword, Integer titles_only) {
		final PaginationResult<Issue> paginationResult = new PaginationResult<Issue>();
		//如果title_only 为1 则表示，只通过标题进行搜索
		final String[] fields = (titles_only != null && titles_only == PAGE_ONE)
				? new String[]{SUMMARY} : new String[]{PKEY, SUMMARY, DESCRIPTION};
		//使用QueryParser查询分析器构造Query对象
		QueryParser qp = new MultiFieldQueryParser(Version.LUCENE_40, fields, analyzer);
		//OR ： 设置以空格分开的短语keyword是'或'的关系;
		qp.setDefaultOperator(QueryParser.Operator.OR);
		//允许使用正则表达式方式,"*" 可模糊查询
		qp.setAllowLeadingWildcard(true);
		Query query;
		try {
			//keyword 可以使用空格隔开,"*" 可模糊查询
			query = qp.parse("*" + keyword + "*");
		} catch (ParseException e) {
			LOGGER.error("create query is error!", e);
			return paginationResult;
		}
		final Map<String, Object> result = searchs.search(query, criteria.getStart(), criteria.getNumber());
		if (result.isEmpty()) {
			return paginationResult;
		} else {
			int count = (Integer) result.get(Searchs.COUNT_MAP_KEY);
			//noinspection unchecked
			final List<Document> documents = (List<Document>) result.get(Searchs.DOCUMENT_MAP_KEY);
			paginationResult.setTotal(count);
			final List<Issue> issues = Lists.newArrayList();
			Issue issue;
			//高亮设置
			final Highlighter highlighter = new Highlighter(_simple_html_formatter, new QueryScorer(query));
			//设置每次返回的字符数
			highlighter.setTextFragmenter(_simpleFragmenter);
			try {
				for (Document document : documents) {
					issue = new Issue();
					issue.setPkey(highlight(PKEY, document, highlighter));

					String issue_type = document.getField("issuetype").stringValue();
					issue.setIssuetype(Integer.valueOf(issue_type == null ? ZERO : issue_type));
					issue.setPname(document.getField("pname").stringValue());

					issue.setAssigneeName(document.getField("assigneeName").stringValue());
					String priopity = document.getField("priopity").stringValue();
					issue.setPriopity(Integer.valueOf(priopity == null ? ZERO : priopity));

					String created = document.getField("created").stringValue();
					issue.setCreated(new Date(Long.valueOf(created == null ? ZERO : created)));

					String updated = document.getField("updated").stringValue();
					issue.setUpdated(new Date(Long.valueOf(updated == null ? ZERO : updated)));
					issue.setSummary(highlight(SUMMARY,document,highlighter));
					issue.setReporter(document.getField("reporter").stringValue());

					issues.add(issue);
				}
			} catch (IOException e) {
				LOGGER.error("fulltext search is eror!,io is error", e);
			} catch (InvalidTokenOffsetsException e) {
				LOGGER.error("fulltext search is eror!,hightlight is error", e);
			}
			paginationResult.setRows(issues);
			return paginationResult;
		}
	}

	@Override
	public boolean reload_index() {
		PaginationCriteria paginationCriteria = new PaginationCriteria(PAGE_ONE, 20, null, null);
		PaginationResult<Issue> issues = issueDao.pageIssue(paginationCriteria);
		int i = PAGE_ONE;
		while (issues.getRows() != null && !issues.getRows().isEmpty()) {
			for (Issue issue : issues.getRows()) {
				create_index(issue);
			}
			paginationCriteria.setStart(i * 20);
			issues = issueDao.pageIssue(paginationCriteria);
			i++;
		}
		return true;
	}
}
